package top.archiesean.common.security.service;

import cn.hutool.core.util.ObjUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import top.archiesean.common.core.utils.gson.GsonUtil;
import top.archiesean.common.redis.biz.RedisService;

import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author ArchieSean
 * @description 令牌存储持久化实现
 * @date 2024-01-03 23:21
 */
@Slf4j
public class RedisOAuth2AuthorizationService implements OAuth2AuthorizationService {
    /**
     * 缓存时间
     */
    private final static Long TIMEOUT = 10L;
    private static final String AUTHORIZATION = "token";

    @Override
    public void save(OAuth2Authorization authorization) {
        Assert.notNull(authorization, "authorization cannot be null");
        //状态判断
        if (matchesState(authorization)) {
            //获取
            String token = authorization.getAttribute(OAuth2ParameterNames.STATE);
            RedisService.set(buildKey(OAuth2ParameterNames.STATE, token), authorization, TIMEOUT, TimeUnit.MINUTES);
        }
        //是否授权码模式
        if (matchesCode(authorization)) {
            OAuth2Authorization.Token<OAuth2AuthorizationCode> oAuth2AuthorizationCodeToken = authorization.getToken(OAuth2AuthorizationCode.class);
            //获取token
            assert oAuth2AuthorizationCodeToken != null;
            OAuth2AuthorizationCode authorizationCodeToken = oAuth2AuthorizationCodeToken.getToken();
            //token 过期时间计算
            long between = ChronoUnit.MINUTES.between(Objects.requireNonNull(authorizationCodeToken.getIssuedAt()), authorizationCodeToken.getExpiresAt());
            RedisService.set(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()), authorization, between, TimeUnit.MINUTES);
        }
        //是否是令牌刷新
        if (matchesRefreshToken(authorization)) {
            assert authorization.getRefreshToken() != null;
            OAuth2RefreshToken oAuth2RefreshToken = authorization.getRefreshToken().getToken();
            //计算令牌过期时间
            long between = ChronoUnit.MINUTES.between(Objects.requireNonNull(oAuth2RefreshToken.getIssuedAt()), oAuth2RefreshToken.getExpiresAt());
            RedisService.set(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, oAuth2RefreshToken.getTokenValue()), authorization
                    , between, TimeUnit.MINUTES);

        }
        //是否是accessToken
        if (matchesAccessToken(authorization)) {
            assert authorization.getAccessToken() != null;
            OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
            long between = ChronoUnit.MINUTES.between(Objects.requireNonNull(accessToken.getIssuedAt()), accessToken.getExpiresAt());
            RedisService.set(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()), authorization, between, TimeUnit.MINUTES);
        }
    }

    @Override
    public void remove(OAuth2Authorization authorization) {
        Assert.notNull(authorization, "authorization cannot be null");
        List<String> keys = new ArrayList<>();
        if (matchesState(authorization)) {
            String token = authorization.getAttribute(OAuth2ParameterNames.STATE);
            keys.add(buildKey(OAuth2ParameterNames.STATE, token));
        }
        if (matchesCode(authorization)) {
            OAuth2Authorization.Token<OAuth2AuthorizationCode> oAuth2AuthorizationCodeToken = authorization.getToken(OAuth2AuthorizationCode.class);
            assert oAuth2AuthorizationCodeToken != null;
            OAuth2AuthorizationCode codeTokenToken = oAuth2AuthorizationCodeToken.getToken();
            keys.add(buildKey(OAuth2ParameterNames.CODE, codeTokenToken.getTokenValue()));
        }
        if (matchesRefreshToken(authorization)) {
            OAuth2RefreshToken oAuth2RefreshToken = Objects.requireNonNull(authorization.getRefreshToken()).getToken();
            keys.add(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, oAuth2RefreshToken.getTokenValue()));
        }
        if (matchesAccessToken(authorization)) {
            OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
            keys.add(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()));
        }
        RedisService.delete(keys.toArray(String[]::new));
    }

    /**
     * 是否是accessToken
     *
     * @param authorization 认证对象
     * @return boolean
     */
    private boolean matchesAccessToken(OAuth2Authorization authorization) {
        return Objects.nonNull(authorization.getAccessToken());
    }

    /**
     * 令牌刷新
     *
     * @param authorization 认证对象
     * @return boolean
     */
    private boolean matchesRefreshToken(OAuth2Authorization authorization) {
        return Objects.nonNull(authorization.getRefreshToken());
    }

    /**
     * @param authorization 认证对象
     * @return boolean
     */
    private boolean matchesCode(OAuth2Authorization authorization) {
        return Objects.nonNull(authorization.getToken(OAuth2AuthorizationCode.class));
    }

    /**
     * 认证状态判断
     *
     * @param authorization 认证对象
     * @return boolean
     */
    private boolean matchesState(OAuth2Authorization authorization) {
        return Objects.nonNull(authorization.getAttribute(OAuth2ParameterNames.STATE));
    }


    /**
     * 缓存key
     *
     * @param type 类别
     * @param id   id
     * @return str
     */
    private String buildKey(String type, String id) {
        return String.format("%s::%s::%s", AUTHORIZATION, type, id);
    }

    @Override
    public OAuth2Authorization findById(String id) {
        throw new UnsupportedOperationException();
    }

    @Override
    public OAuth2Authorization findByToken(String token, OAuth2TokenType tokenType) {
        Assert.hasText(token, "token cannot be empty");
        Assert.notNull(tokenType, "tokenType cannot be empty");
        return (OAuth2Authorization) RedisService.getObject(buildKey(tokenType.getValue(), token));
    }
}
